import {hasOwn, isPlainObject, extend, isDef, isArray} from "../../shared/util";
import {set} from "../observer/index";
import {ASSET_TYPES, LIFECYCLE_HOOKS} from "../../shared/constants";

/**
 * 合并父组件实例的 data 与 子组件实例的 data
 * 以孩子组件实例的数据优先，父组件实例数据被覆盖
 * @param {Object} parentData
 * @param {Object} childData
 * @return {Object}
 */
function mergeData (parentData, childData) {
    const keys = Object.keys(parentData);
    for (let i=0; i<keys.length; i++) {
        const key = keys[i];
        if (key === '__ob__') {
            continue;
        }

        let childValue = childData[key];
        let parentValue = parentData[key];
        if (!hasOwn(childData, key)) {
            set(childData, key, parentValue);
        }
        else {
            if (childValue !== parentValue
                && isPlainObject(childValue)
                && isPlainObject(parentValue)) {
                mergeData(parentValue, childValue);
            }
        }
    }
    return childData;
}

/**
 * 对父组件和子组件的 data 函数进行合并处理，返回新的 data 函数
 * @param {Function} parentDataFunc 父组件 data 函数
 * @param {Function} childDataFunc 子组件 data 函数
 * @return {function} 合并后的 data 函数
 */
function mergeDataToFn (parentDataFunc, childDataFunc) {
    if (!childDataFunc) {
        return parentDataFunc;
    }
    if (!parentDataFunc) {
        return childDataFunc;
    }
    // 返回新的 data 函数
    return function mergeDataFn () {
        return mergeData(
            parentDataFunc === 'function' ? parentDataFunc.call(this, this) : parentDataFunc,
            childDataFunc === 'function' ? childDataFunc.call(this, this) : childDataFunc
        );
    };
}

/**
 * 属性合并规则，子组件覆盖父组件
 * @param {Object} parentVal
 * @param {Object} childVal
 * @return {object}
 */
function mergeProperty (parentVal, childVal) {
    if (!childVal) {
        return parentVal;
    }
    if (!parentVal) {
        return childVal;
    }
    let ret = {};
    ret = extend(ret, parentVal);
    ret = extend(ret, childVal);
    return ret;
}

/**
 * 返回去重后的 hooks 数组
 * @param {Array<Function>} hooks
 */
function dedupeHooks (hooks) {
    let hook = null;
    for (let i=0; i<hooks.length; i++) {
        hook = hooks[i];
        if (
            (hooks.indexOf(hook) !== -1 && hooks.indexOf(hook) !== i)
            || !isDef(hook)
        ) {
            hooks.splice(i, 1);
        }
    }
    return hooks;
}

/**
 * 对于 Hook 采取合并的策略，不进行覆盖
 * @param {Array<Function>} parentHook
 * @param {Array<Function>|Function} childHook
 */
function mergeHook (parentHook, childHook) {
    // 处理 parentHook 为 null 的情况
    if (!isDef(parentHook)) {
        parentHook = [];
    }
    // 组装合并结果
    let res = null;
    res = Array.isArray(childHook)
        ? parentHook.concat(childHook)
        : [childHook].concat(parentHook);
    // 排除重复的 hook
    return res ? dedupeHooks(res) : res;
}

/**
 * 对于 asset 的合并，采用子组件覆盖父组件
 * @param {Object} parentAsset
 * @param {Object} childAsset
 */
function mergeAsset (parentAsset, childAsset) {
    let res = Object.create(parentAsset || null);
    if (childAsset) {
        res = extend(res, childAsset);
    }
    return res;
}

/**
 * 默认合并策略函数
 * @param {Object} parent 父组件配置
 * @param {Object} child 子组件配置
 * @param {String} key 配置键值
 * @return {*} 返回的值
 */
function defaultStrategy (parent, child, key) {
    return child[key] === undefined ? parent[key] : child[key];
}

// 对一些典型的属性的合并策略
const strategies = {
    data: mergeDataToFn,
    props: mergeProperty,
    methods: mergeProperty,
};

LIFECYCLE_HOOKS.forEach(hook => {
    strategies[hook] = mergeHook;
});

ASSET_TYPES.forEach(asset => {
    strategies[asset] = mergeAsset;
});

/**
 * 根据属性选择相应的策略进行配置的合并
 * @param {Object} options 保存合并结果的配置对象
 * @param {String} key 配置的键值
 * @param {Object} parent 父组件配置
 * @param {Object} child 子组件配置
 */
function mergeField (options, key, parent, child) {
    const strategy = strategies[key];
    if (strategy) {
        options[key] = strategy(parent[key], child[key]);
    }
    else {
        options[key] = defaultStrategy(parent, child, key);
    }
}

/**
 * 对父组件和子组件的不同属性采取相应的策略进行合并
 * @param {Object} parent 父组件的配置
 * @param {Object} child 子组件的配置
 * @return {object} 保存合并结果的配置对象
 */
export function mergeOptions(parent, child) {
    let options = {};
    for (let key in parent) {
        mergeField(options, key, parent, child);
    }
    for (let key in child) {
        if (!hasOwn(parent, key)) {
            mergeField(options, key, parent, child);
        }
    }
    return options;
}
